// 定位引擎
import {createPopper,popperGenerator,detectOverflow} from '@popperjs/core';
// 通用css
import 'normalize.css';
// 动画库
import 'animate.css';
// 提示工具
import 'tippy.js/dist/tippy.css';
import 'tippy.js/animations/scale.css';
import 'tippy.js/animations/shift-away.css';
import 'tippy.js/animations/shift-toward.css';
import 'tippy.js/animations/perspective.css';
import 'tippy.js/dist/backdrop.css';
import tippy,{animateFill,hideAll} from 'tippy.js';
// http库
import axios from 'axios';
window.detectOverflow=detectOverflow;
/**
 * 2019-12
 * @module utils
 * @author 唐国雄
 * utils
 * 		isObject				是否是狭义的对象
 * 		baseType				是否是(null,undefined,number,boolean,string,function)
 * 		baseTypeNotNull			是否是(number,boolean,string,function)
 * 		convert					转换器生成
 * 		moneyFormat				格式化金额
 * 		moneyConvert			转换金额为指定单位的数值
 * 		moneyDivide				格式化金额(不考虑单位)
 * 		addResizeCallBack		增加窗口改变大小时的回调函数
 * 		extend					合并多个对象
 * 		defaultTemplate			设置默认值模板
 * 		storageSet				向localStorage存储任意数据
 * 		storageGet				向localStorage获取存储的数据
 * 		listToTree				把列表转换为树结构
 * 		getCalcColumnWidthFn	通过宽度表达式列表,返回宽度列表生成器
 * 		parseDate				解析日期
 * 		formatDate				格式化日期
 * 		debounced				防抖函数
 */
/** @global */
tippy.hideAll=hideAll;
const utils={
	createPopper,
	popperGenerator,
	detectOverflow,
	tippy,
	animateFill,
	axios
};
/**
 * 增加原型区方法(如果没有原生提供)
 * @param _ 原型区
 * @param {String} name 方法名
 * @param {Function} fn 方法
 */
function define(_,name,fn){
	if(!_[name]){
		Object.defineProperty(_,name,{
			value:fn
		});
	}
}
const polyfills=[
	{
		type:Array.prototype,
		/**
		 * @alias Array.prototype
		 */
		methods:{
			/**
			 * 删除数组中第一个指定值
			 * @param {Object} val 删除的值
			 * @return {Array} 本身
			 */
			remove(val){
				const index=this.indexOf(val);
				if(index!==-1){
					this[index]=this[this.length-1];
					this.length-=1;
				}
				return this;
			}
		}
	}
];
for(const polyfill of polyfills){
	const type=polyfill.type;
	const methods=polyfill.methods;
	for(const key of Object.keys(methods)){
		define(type,key,methods[key]);
	}
}
const onresizeCallBack=[]; // 窗口改变大小时,需要执行的回调
/**
 * 把金额转换为以逗号分隔的格式
 * @alias utils.moneyFormat
 * @param {Object} unit 金额单位
 * @param {Number} unit.mu_basenum 金额基数
 * @param {Number} unit.mu_point 金额保留小数位数
 * @param {String|Number} value 金额
 * @return {String} 以逗号分隔的金额字符串
 */
utils.moneyFormat=function(unit,value){
	return moneyDivide(unit,moneyConvert(unit,value));
};
/**
 * 转为为指定单位的金额
 * @alias utils.moneyConvert
 * @param {Object} unit 金额单位
 * @param {Number} unit.mu_basenum 金额基数
 * @param {String|Number} value 金额
 * @returns {Number} 指定单位的金额数值
 */
function moneyConvert(unit,value){
	return parseFloat(value||0)/unit.mu_basenum;
}
utils.moneyConvert=moneyConvert;
/**
 * 把金额转换为以逗号分隔的格式(不考虑单位)
 * @alias utils.moneyDivide
 * @param {Object} unit 金额单位
 * @param {Number} unit.mu_point 金额保留小数位数
 * @param {String|Number} value 金额
 * @returns {String} 以逗号分隔的金额字符串
 */
function moneyDivide(unit,value){
	const splits=parseFloat(value||0).toFixed(unit.mu_point).split('.');
	return splits[0].replace(/\d{1,3}(?=(?:\d{3})+$)/g,'$&,')+(splits[1]?('.'+splits[1]):'');
}
utils.moneyDivide=moneyDivide;
/**
 * 增加窗口改变大小时,需要执行的任意个回调
 * @alias utils.addResizeCallBack
 * @param {...Function} callback 回调函数
 */
utils.addResizeCallBack=function(callback){
	for(const argument of arguments){
		onresizeCallBack.push(argument);
	}
};
/**
 * 狭义对象(不包括数组)
 * @alias utils.isObject
 * @param {*} val 测试对象
 * @return {Boolean} 是否满足
 */
function isObject(val){
	return val!=null&&typeof val==='object'&&!Array.isArray(val);
}
utils.isObject=isObject;
/**
 * 基本类型(null,undefined,number,boolean,string,function等可以直接赋值的)
 * @alias utils.baseType
 * @param {*} val 测试对象
 * @return {Boolean} 是否满足
 */
function baseType(val){
	return val==null||typeof val!=='object';
}
utils.baseType=baseType;
/**
 * 不是null和undefined的基本类型
 * @alias utils.baseTypeNotNull
 * @param {*} val 测试对象
 * @return {Boolean} 是否满足
 */
function baseTypeNotNull(val){
	return val!=null&&typeof val!=='object';
}
utils.baseTypeNotNull=baseTypeNotNull;
/**
 * 深度拷贝
 * dest,src要么同为数组,要么同为对象!(外部控制)
 * @param {Object} dest 目标对象,会被改变
 * @param {Object} src 被拷贝的对象
 */
function copy(dest,src){
	if(Array.isArray(src)||src instanceof ReplaceArr){
		// dest,src都是数组
		if(src instanceof ReplaceArr){
			// 替换模式
			for(const [index,value] of src.array.entries()){
				copyInner(dest,value,index);
			}
		}else{
			// 新增模式
			for(const ele of src){
				if(baseType(ele)){
					dest.push(ele);
				}else if(ele instanceof Date){
					dest.push(new Date(+ele));
				}else if(ele instanceof RegExp){
					const regexp=new RegExp(ele.source,/\w*$/.exec(ele));
					regexp.lastIndex=ele.lastIndex;
					dest.push(regexp);
				}else if(ele instanceof Set||ele instanceof Map){
					dest.push(new ele.constructor(ele));
				}else{
					// 为数组或对象建立其副本(避免拷贝引用)
					const newEle=new ele.constructor();
					copy(newEle,ele);
					dest.push(newEle);
				}
			}
		}
	}else{
		// dest,src都是对象
		for(const [key,value] of Object.entries(src)){
			copyInner(dest,value,key);
		}
	}
}
function copyInner(dest,value,key){
	if(baseType(value)){
		dest[key]=value;
	}else if(value instanceof Date){
		dest[key]=new Date(+value);
	}else if(value instanceof RegExp){
		const regexp=new RegExp(value.source,/\w*$/.exec(value));
		regexp.lastIndex=value.lastIndex;
		dest[key]=regexp;
	}else if(value instanceof Set||value instanceof Map){
		dest[key]=new value.constructor(value);
	}else{
		// 被拷贝值为数组或对象
		const oldValue=dest[key];
		if(
			baseType(oldValue)||
			(
				(Array.isArray(value)||value instanceof ReplaceArr)!==Array.isArray(oldValue)
			)
		){
			/**
			 * 当以下情况时丢弃原有值:
			 * 1.原有值为基本类型
			 * 2.原有值和被拷贝值,类型不相同时
			 */
			dest[key]=new value.constructor();
		}
		copy(dest[key],value);
	}
}
/**
 * 合并多个对象(深度拷贝),后面覆盖前面,会影响第一个对象
 * @alias utils.extend
 * @example 数组新增模式
 * utils.extend({a:1,b:1,c:[1,2]},{b:2,c:[3,4],d:5});
 * //返回{a:1,b:2,c:[1,2,3,4],d:5}
 * @example 数组扩展模式
 * utils.extend({a:1,b:1,c:[1,2]},{b:2,c:new utils.ReplaceArr([3,4,6]),d:5});
 * //返回{a:1,b:2,c:[3,4,6],d:5}
 * @param {Object} target 合并到的对象(被改变)
 * @param {...Object} srcs 被拷贝的对象
 * @return {Object} 合并到的对象
 */
utils.extend=function(target,...srcs){
	if(baseType(target)){
		throw new TypeError('target必须是对象或数组!');
	}
	const isArray=Array.isArray(target);
	for(const src of srcs){
		/**
		 * 当以下情况时忽略拷贝:
		 * 1.被拷贝对象为基本类型时
		 * 2.被拷贝对象和目标对象,类型不相同时
		 */
		if(
			baseType(src)||
			(
				(Array.isArray(src)||src instanceof ReplaceArr)!==isArray
			)
		) continue;
		copy(target,src);
	}
	return target;
};
/**
 * 用在utils.extend中被拷贝的对象,表示扩展数组对应的元素,当不存在对应元素再新增
 * @param {Array} arr 数据
 * @constructor
 */
function ReplaceArr(arr){
	/**
	 * @alias utils.ReplaceArr.prototype.array
	 */
	this.array=arr;
}
ReplaceArr.prototype=Object.create(Array.prototype);// 为了constructor指向Array
utils.ReplaceArr=ReplaceArr;
/**
 * @callback converter
 * @param {*} val 需要转换的值
 */
/**
 * 转换为指定实例的类型的转换器
 * @alias utils.convert
 * @param {*} type 目标类型的实例
 * @return {converter} 转换器
 */
function convert(type){
	switch(typeof type){
		case 'boolean':
			return function(val){
				if(val==='false') return false;
				return Boolean(val);
			};
		case 'number':
			return function(val){
				return Number(val);
			};
		case 'string':
			return function(val){
				return String(val);
			};
		case 'function':
			return function(val){
				if(typeof val!=='function'){
					return function(){
						return val;
					};
				}
				return val;
			};
		default:
			return function(val){
				return val;
			};
	}
}
utils.convert=convert;
/**
 * @callback templateCallBack
 * @param {*} data 当前自定义处理器的收到的原始数据
 * @return {*} 处理后的数据
 */
/**
 * 默认值模板的自定义处理函数
 * @alias utils.Callback
 * @constructs utils.Callback
 * @param fn {templateCallBack} 自定义处理函数
 * @constructor
 */
function Callback(fn){
	if(typeof fn!=='function') throw new TypeError('参数必须是函数!');
	/**
	 * @alias utils.Callback.prototype.callback
	 */
	this.callback=fn;
}
utils.Callback=Callback;
/**
 * 定义src的默认值格式,自动转换为默认值的类型(当提供的数据为null或undefined则启用默认值)
 * @alias utils.defaultTemplate
 * @example 基本类型默认值
 * utils.defaultTemplate(0,undefined); utils.defaultTemplate(0,null);
 * //返回0
 * utils.defaultTemplate(0,10); utils.defaultTemplate(0,'10');
 * //返回10
 * @example 数组默认值
 * utils.defaultTemplate([1],[10,'10',null,undefined,false]);
 * //返回[10,10,1,1,0]
 * utils.defaultTemplate([{a:1}],[{},null,undefined,{a:3}]);
 * //返回[{a:1},{a:1},{a:1},{a:3}]
 * @example 对象默认值
 * utils.defaultTemplate({a:1,'?':0},{b:null,c:{},d:[],f:10});
 * //返回{a:1,b:0,c:0,d:0,f:10}
 * utils.defaultTemplate({a:1,'?':{q:1}},{a:2,b:3,c:{q:3}});
 * //返回{a:2,b:{q:1},c:{q:3}}
 * @example 自定义处理函数
 * utils.defaultTemplate({a:new utils.Callback(function(d){return d*2})},{a:20});
 * //返回{a:40}
 * @param {*} def 模板,数组中的对象表示对每个元素的模板,对象中的'?'键表示适配所有键
 * @param {*} src 原数据
 * @return {*} 处理后的数据
 */
function defaultTemplate(def,src){
	if(baseType(def)){
		return baseTypeNotNull(src)?convert(def)(src):def;
	}else if(def instanceof Callback){
		return def.callback(src);
	}else if(Array.isArray(def)){
		const arr=[];
		if(Array.isArray(src)){
			if(def.length){
				const def1=def[0];
				for(const ele of src){
					arr.push(defaultTemplate(def1,ele));
				}
			}else{
				copy(arr,src);
			}
		}
		return arr;
	}else{
		const keys=Object.keys(def);
		const obj={};
		if(isObject(src)){
			const srcKeys=Object.keys(src);
			let isAllTemplate=false;
			let allTemplate;
			for(const key of keys){
				if(key==='?'){
					isAllTemplate=true;
					allTemplate=def[key];
					continue;
				}
				if(srcKeys.includes(key)){
					srcKeys.remove(key);
					obj[key]=defaultTemplate(def[key],src[key]);
				}else{
					obj[key]=defaultTemplate(def[key],undefined);
				}
			}
			if(isAllTemplate){
				for(const key of srcKeys){
					obj[key]=defaultTemplate(allTemplate,src[key]);
				}
			}
		}else{
			for(const key of keys){
				if(key==='?') continue;
				obj[key]=defaultTemplate(def[key],undefined);
			}
		}
		return obj;
	}
}
utils.defaultTemplate=defaultTemplate;
/**
 * 向localStorage存储任意数据
 * @alias utils.storageSet
 * @param {String} key 键名
 * @param {*} value 值
 * @param {Boolean} [storeFn] 是否存储函数
 */
utils.storageSet=function(key,value,storeFn){
	localStorage.setItem(key,storeFn?JSON.stringify(value,function(k,v){
		if(typeof v==='function'){
			return v.toString().replace(/^\s+|\s+$/mg,'');
		}
		return v;
	}):JSON.stringify(value));
};
/**
 * 向localStorage获取存储的数据
 * @alias utils.storageGet
 * @param {String} key 键名
 * @returns {*} 值
 * @param {Boolean} [storeFn] 是否存储函数
 */
utils.storageGet=function(key,storeFn){
	const val=localStorage.getItem(key);
	if(val==='undefined') return undefined;
	return storeFn?JSON.parse(val,function(k,v){
		if(typeof v==='string'&&v.indexOf('function(')===0){
			// eslint-disable-next-line no-new-func
			return Function('"use strict";return '+v)();
		}
		return v;
	}):JSON.parse(val);
};
/**
 * 删除localStorage的键
 * @param {String} key 键名
 */
utils.storageDel=function(key){
	localStorage.removeItem(key);
};
/**
 * 把列表转换为树结构
 * @alias utils.listToTree
 * @param {Array} nodeList 列表
 * @param {String} pIdAttr 父节点的id属性名
 * @param {String} idAttr 本节点的id属性名
 * @param {*} rootId 根节点的id值
 * @param {String} sonAttr 子节点数组的属性名
 * @returns {[]} 树结构
 */
utils.listToTree=function(nodeList,pIdAttr,idAttr,rootId,sonAttr){
	const nodeTree=[];
	for(const node1 of nodeList){
		const pid=node1[pIdAttr];
		if(pid!==rootId){
			for(const node2 of nodeList){
				if(pid===node2[idAttr]){
					const sons=node2[sonAttr];
					if(sons){
						sons.push(node1);
					}else{
						node2[sonAttr]=[node1];
					}
					break;
				}
			}
		}
	}
	for(const node of nodeList){
		if(node[pIdAttr]===rootId){
			nodeTree.push(node);
		}
	}
	return nodeTree;
};
/**
 * 给定总宽度,返回宽度值列表
 * @callback CalcColumnWidthFn
 * @param {Number} width 总宽度
 * @return {Number[]} 宽度值列表
 */
/**
 * 通过宽度表达式列表,返回宽度列表生成器
 * Number表示固定宽度px
 * String中可以用百分比表示占总宽的百分比,fr表示剩余宽度的权重
 * @alias utils.getCalcColumnWidthFn
 * @example
 * utils.getCalcColumnWidthFn([100,'10%+50','2fr','1fr+10%+50'])(1000);
 * //返回[100, 150, 400, 350]
 * @param {(Number|String)[]} columnWidths
 * @return {CalcColumnWidthFn} 宽度列表生成器
 */
utils.getCalcColumnWidthFn=function(columnWidths){
	let lastCalc;
	const arr=columnWidths.map(function(arg,index){
		arg=arg||'1fr';
		if(typeof arg==='number'){
			return arg;
		}else{
			if(arg.includes('fr')){
				lastCalc=index;
			}
			// eslint-disable-next-line no-new-func
			return Function('width','frwidth','"use strict";width/=100;return '+arg.replace(/(\d+)(?:%|(fr))/g,'$1*$2width'));
		}
	});
	let frCount=0;
	for(const ele of arr){
		if(typeof ele==='function'){
			frCount+=ele(0,1)-ele(0,0);
		}
	}
	return function(width){
		width=Math.floor(width);
		let remain=width;
		if(frCount){
			for(const ele of arr){
				if(typeof ele==='number'){
					remain-=ele;
				}else{
					remain-=ele(width,0);
				}
			}
			remain/=frCount;
		}
		let total=0;
		const res=arr.map(function(ele){
			if(typeof ele==='number'){
				total+=ele;
				return ele;
			}else{
				const value=Math.floor(ele(width,remain));
				total+=value;
				return value;
			}
		});
		if(lastCalc!=null){
			res[lastCalc]+=width-total;
		}
		return res;
	};
};
/**
 * 解析日期
 * @alias utils.parseDate
 * @param {String} dateStr 格式为yyyy-MM-ddTHH:mm:ss.SSSZ,如: '2020-05-21','2020-05-21T11:01'
 * @return {Date} 解析后的日期
 */
utils.parseDate=function(dateStr){
	switch(dateStr.length){
		case 4:
			dateStr+='-01';
		// eslint-disable-next-line no-fallthrough
		case 7:
			dateStr+='-01';
		// eslint-disable-next-line no-fallthrough
		case 10:
			dateStr+='T00:00';
	}
	return new Date(dateStr);
};
/**
 * 格式化日期
 * @alias utils.formatDate
 * @param {Date} date 需要格式化的日期
 * @return {string} 格式化后的字符串yyyy-MM-ddTHH:mm:ss.SSSZ
 */
utils.formatDate=function(date){
	return new Date(date.getTime()-date.getTimezoneOffset()*60*1000).toISOString();
};
/**
 * 防抖函数的实际函数
 * @callback DebouncedFn
 * @param {...*} args 调用utils.debounced传的args和调用防抖函数传的参数
 */
/**
 * 防抖函数
 * @alias utils.debounced
 * @param {DebouncedFn} fn 实际执行的函数
 * @param {Number} wait 等待的毫秒数
 * @param {Object} [thisArg] fn函数的this,不传的时候获取执行环境的this
 * @param {...*} [args] 传给fn函数的参数
 * @return {Function} 返回的防抖函数
 */
function debounced(fn,wait,thisArg,...args){
	let timeout;
	return function(){
		clearTimeout(timeout);
		timeout=setTimeout(fn.bind(thisArg||this,...args,...arguments),wait);
	};
}
utils.debounced=debounced;
/**
 * 比较器
 * @callback ValueComparator
 * @param {*} ele1	左边的元素
 * @param {*} ele2	右边的元素
 * @return {Boolean} true,表示ele1排在ele2前
 */
/**
 * 归并排序(稳定)
 * @param {Array} arr 需要排序的数组
 * @param {Number} left 排序开始索引
 * @param {Number} right 排序结束索引
 * @param {ValueComparator} comparator 比较器
 * @return {[]|*[]}
 */
function mergeSort(arr,left,right,comparator){
	if(left+1===right){
		const eleLeft=arr[left];
		const eleRight=arr[right];
		if(comparator(eleLeft,eleRight)){
			return [eleLeft,eleRight];
		}else{
			return [eleRight,eleLeft];
		}
	}else if(left<right){
		const mid=Math.floor((left+right)/2);
		return sortedMerge(
			mergeSort(arr,left,mid,comparator),
			mergeSort(arr,mid+1,right,comparator),
			comparator
		);
	}else if(left>right){
		return [];
	}
	return [arr[left]];
}
/**
 * 合并2个有序的数组
 * @param {Array} arr1 数组1
 * @param {Array} arr2 数组2
 * @param {ValueComparator} comparator 比较器
 * @return {[]}
 */
function sortedMerge(arr1,arr2,comparator){
	const result=[];
	let i=0;
	const len1=arr1.length;
	let j=0;
	const len2=arr2.length;
	while(i<len1&&j<len2){
		if(comparator(arr1[i],arr2[j])){
			result.push(arr1[i++]);
		}else{
			result.push(arr2[j++]);
		}
	}
	while(i<len1){
		result.push(arr1[i++]);
	}
	while(j<len2){
		result.push(arr2[j++]);
	}
	return result;
}
utils.mergeSort=mergeSort;
utils.sortedMerge=sortedMerge;
/**
 * 比较字符串表示的数字
 * @param {String} a 数字1
 * @param {String} b 数字2
 * @return {number} 返回-1表示a<b, 0表示a===b, 1表示a>b
 */
function compareStringNumber(a,b){
	if(a===b){
		return 0;
	}else{
		const aSymbol=a[0]==='-'?-1:1;
		const bSymbol=b[0]==='-'?-1:1;
		if(aSymbol===bSymbol){
			var aLen=a.length;
			var bLen=b.length;
			if(aLen<bLen){
				return -1*aSymbol;
			}else if(aLen>bLen){
				return aSymbol;
			}else{
				for(var i=0;i<aLen;++i){
					if(a[i]!==b[i]){
						return (Number(a[i])<b[i]?-1:1)*aSymbol;
					}
				}
			}
		}else{
			return aSymbol;
		}
	}
}
utils.compareStringNumber=compareStringNumber;
/**
 * 窗口改变大小时,需要执行的回调
 */
window.addEventListener('resize',debounced(function(onresizeCallBack){
	for(const callBack of onresizeCallBack){
		callBack();
	}
},100,null,onresizeCallBack));
export default utils;
